﻿module VirtualCity.Scripts.Layer1

open Casanova
open Casanova.Utilities
open Casanova.Core
open Casanova.Coroutines
open Casanova.Game
open Casanova.Input
open Casanova.Math
open Casanova.Drawing
open VirtualCity.City
open VirtualCity.CityDescription
open VirtualCity.Plan
open VirtualCity.PlanDescription
open Wintellect.PowerCollections


let inline (!) x = immediate_lookup x


let stats_acceptable (initial_stats:Stats<1>) (current_stats:Stats<1>) =
  if initial_stats.Rested <= 0.0f ||
     initial_stats.Sated <= 0.0f then 
    current_stats.Rested > 0.1f &&
    current_stats.Sated > 0.1f
  else
    current_stats.Rested > 0.3f &&
    current_stats.Sated > 0.3f


let update_city (time_elapsed:float32<s>) (city:CityDescription) (stats_loss_mask:Stats<1>) (agent:AgentDescription) = 
  let clock = city.Clock
  let new_clock = { Time = clock.Time + TimeSpan.FromSeconds(time_elapsed |> float) }
  let new_buses =
    [
      for p,b,stops in city.Buses do
        match stops |> Seq.tryFind (fun s -> s.Time >= new_clock.Time) with
        | Some s -> yield s.Location,b,stops
        | None -> yield p,b,stops
    ]
  { city with Clock = new_clock
              Buses = new_buses },
  { agent with Stats = agent.Stats - Stats.Multiply(agent.IdleStatsLoss * time_elapsed, stats_loss_mask) }


let layer1_preconditions_ok (city:CityDescription) (agent:AgentDescription) (action:Layer1ActionDescription) =
  let clock = city.Clock
  match action with
  | Layer1ActionDescription.Walk target -> true
//  | Layer1ActionDescription.Board(bus,source,destination) -> 
//    let target_distance = Vector2.Distance(source.Location, destination.Location)
//    let time_elapsed = if target_distance > 0.0f<m> then target_distance / bus.MaxSpeed else 0.0f<s>
//    bus.TicketPrice <= agent.Money


let layer1_simulate_action (city:CityDescription) (agent:AgentDescription) (action:Layer1ActionDescription) = 
  match action with
  | Layer1ActionDescription.Walk target -> 
    let target_distance = Vector2.Distance(target, agent.Position)
    let time_elapsed = if target_distance > 0.0f<m> then 2.0f * target_distance / agent.MaxSpeed else 0.0f<s>
    let moved_agent = 
      {
        agent with Position = target
      }
    let updated_city,moved_agent = update_city time_elapsed city Stats<1>.One moved_agent
    { Agent = moved_agent; City = updated_city }
//  | Layer1ActionDescription.Board (bus,src,dst) -> 
//    let time_elapsed = (dst.Time - city.Clock.Time).TotalSeconds |> float32 |> with_measure
//    let moved_agent = 
//      {
//        agent with Position = dst.Location
//      }
//    let updated_city,moved_agent = update_city time_elapsed city Stats<1>.One moved_agent
//    { Agent = moved_agent; City = updated_city }


let targets_nearby (final_destination:Vector2<m>) (city:CityDescription) (agent:AgentDescription) = 
  let is_walkable (n:WalkNode) = 
    n.IsBuilding |> not || Vector2.Distance(n.Position,final_destination) <= 1.0f<m>
  seq{
    yield agent.Position
    for (x,y) in city.WalkGraph.Links do
      if Vector2.Distance(x.Position,agent.Position) <= 1.0f<m> && y |> is_walkable then
        yield y.Position
      if Vector2.Distance(y.Position,agent.Position) <= 1.0f<m> && x |> is_walkable then
        yield x.Position
//    for _,b in city.Buildings do
//      let distance = Vector2.Distance(b.Position, agent.Position)
//      if distance <= 1000.0f<m> then
//        yield b.Position
  } |> Seq.toList // |> Seq.groupBy id |> Seq.map (fun (l,_) -> l) |> Seq.toList


//let buses_nearby (city:CityDescription) (agent:AgentDescription) = 
//  seq{
//    for _,b,stops in city.Buses do
//      for s in stops do
//        if s.Time >= city.Clock.Time && Vector2.Distance(s.Location, agent.Position) <= 10.0f<m> then
//          for s' in stops do
//            if s'.Time > s.Time && 
//               Vector2.Distance(s.Location,s'.Location) > 100.0f<m> then
//              yield b,s,s'
//  }


let layer1_reaches_goal (source:Vector2<m>) (target:Vector2<m>) (plan:PlanDescription<Layer1ActionDescription>) =
  let last_step = plan.Steps.Head
  let agent = last_step.Expectations.Agent
  let stats_ok = stats_acceptable plan.Steps.Last.Expectations.Agent.Stats agent.Stats
  Vector2.Distance(agent.Position, target) <= 2.0f<m> && stats_ok


let layer1_is_acceptable (target_time:DateTime) (plan:PlanDescription<Layer1ActionDescription>) =
  let last_step = plan.Steps.Head
  let agent = last_step.Expectations.Agent
  let stats_ok = stats_acceptable plan.Steps.Last.Expectations.Agent.Stats agent.Stats
  plan.Steps.Length <= 20 && stats_ok


let layer1_compare_plans (source:Vector2<m>) (target:Vector2<m>) (p1:PlanDescription<Layer1ActionDescription>) (p2:PlanDescription<Layer1ActionDescription>) =
  let w1n = (p1.Steps |> List.head).Expectations
  let w2n = (p2.Steps |> List.head).Expectations  
  let d1 = Vector2.Distance(w1n.Agent.Position, target)
  let d2 = Vector2.Distance(w2n.Agent.Position, target)
  if d1 < d2 then 1
  elif d1 > d2 then -1
  else 0

